/*
 * Copyright 2012 Quytelda K. Gaiwin
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.tamalin.babel;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Scanner;
import java.util.logging.*;
import java.util.regex.*;
import org.tamalin.babel.engine.SequenceGenerator;

/**
 * Created: Aug 13, 2011
 * @author Quytelda Gaiwin
 */
public class Main {

	/**
	 * @param args the command line arguments
	 */
	public static void main(String[] args)
	{
		// Make sure arguments are provided
		// If not, complain and exit
		if(args.length == 0)
		{
			System.err.println("Wrong number of arguments!");
			System.out.println("Try --help");
			return;
		}

		// Define program variables that will be set by the program arguments.
		// Most of these are null unless otherwise specified.
		int quantity = 1;
		String template = null;
		Pattern include = null;
		Pattern exclude = null;
		File defs = null;
		PrintStream out = System.out;

		// Parse command line arguments
		for(int i = 0; i < args.length; i++)
		{
			if(args[i].equals("-h") || args[i].equals("--help"))
			{
				// Output help information and exit
				// Displays a list of options and program usage information
				printHelp();
				return;
			}
			else if(args[i].equals("-v") || args[i].equals("--verbose"))
			{
				// Set logger level to FINEST to display ALL program messages.
				// We also need to set the level on the console to ensure the messages are printed
				Logger classLogger = Logger.getLogger(Main.class.getPackage().getName());
				classLogger.setLevel(Level.FINEST);
				ConsoleHandler handler = new ConsoleHandler();
				handler.setLevel(Level.FINEST);
				classLogger.addHandler(handler);
				classLogger.setUseParentHandlers(false);
			}
			else if(args[i].equals("-q") || args[i].equals("--quiet"))
			{
				// Set logger level to FINEST to display ALL program messages.
				// We also need to set the level on the console to ensure the messages are printed
				Logger classLogger = Logger.getLogger(Main.class.getPackage().getName());
				classLogger.setLevel(Level.WARNING);
			}
			else if(args[i].equals("-V") || args[i].equals("--version"))
			{
				// Print version information, then exit.
				printVersion(true);
				System.exit(0);
			}
			else if(args[i].equals("-s") || args[i].equals("--strict"))
			{
				// Set strict checking mode for template integrity
				strict = true;
			}
			else if(args[i].equals("-n") || args[i].equals("--number"))
			{
				// Set quantity of sequences to generate
				quantity = Integer.parseInt(args[++i]);
			}
			else if(args[i].equals("-d") || args[i].equals("--definitions"))
			{
				// set custom definitions  file
				defs = new File(args[++i]);
			}
			else if(args[i].equals("-o") || args[i].equals("--output"))
			{
				// redirect the programs output of sequences to a disk file
				File outfile = new File(args[++i]);

				// check if the file exists
				if(outfile.exists())
				{
					// ensure overwriting is OK
					System.out.print("The file " + outfile.getPath() + " already exists.  Do you want to overwrite it? [y/N]: ");
					Scanner in = new Scanner(System.in);
					boolean abort = false;
					if(in.hasNext())
						abort = !in.next().equals("y");

					if(abort)  // abort
					{
						System.out.println("Aborting...");
						System.exit(0);
					}
				}

				// attempt to redirect the output, or log any exceptions
				try
				{
					out = new PrintStream(new FileOutputStream(outfile));
				}
				catch (FileNotFoundException ex)
				{
					Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
			else if(args[i].equals("-i") || args[i].equals("--include"))
			{
				// set the REGEX pattern that will be used to filter sequences (for inclusion)
				// ensure the pattern is syntactically correct, or it will be ignored
				try
				{
					include = Pattern.compile(args[++i]);
				}
				catch(PatternSyntaxException ex)
				{
					Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Invalid REGEX pattern will be ignored!", ex);
				}
			}
			else if(args[i].equals("-x") || args[i].equals("--exclude"))
			{
				// set the REGEX pattern that will be used to filter sequences (for exclusion)
				// ensure the pattern is syntactically correct, or it will be ignored
				try
				{
					exclude = Pattern.compile(args[++i]);
				}
				catch(PatternSyntaxException ex)
				{
					Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Invalid REGEX pattern will be ignored!", ex);
				}
			}
			else
			{	
				// complain about unrecognized arguments
				if(i < args.length - 1)
					Logger.getLogger(Main.class.getName()).log(Level.WARNING, "Unrecognized argument: {0}", args[i]);
			}
		}

		// get the template and ensure it is valid
		// otherwise, complain and exit
		template = args[args.length - 1];
		if(template.length() == 0 || template.startsWith("-"))
		{
			Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "No template!");
			System.exit(0);
		}

		/* Now that the environment is in order, begin parsing user input
		   This is where the real work begins. */

		// Create a new SequenceGenerator object that uses the definitions file defs
		SequenceGenerator generator = new SequenceGenerator(defs);

		// use generator to generate the specified number of sequences
		for(int i = 0; i < quantity; i++)
		{
			// generate a sequence from the template
			String result = generator.generate(template);

			// Include or exclude generated results according to REGEX rules
			if(include != null && !include.matcher(new String(result)).matches()) continue;
			else if(exclude != null && exclude.matcher(new String(result)).matches()) continue;

			// At this point in execution the string has passed the REGEX filter
			// Send it to the output stream
			out.println(result);
		}
	}

	/** Returns whether or not strict checking is enabled
	    in the current runtime instance of the program. */
	public static boolean isStrict()
	{
		return strict;
	}

	/**
	* Prints the program's help information from the file org/tamalin/babel/resources/help.txt
	* Help information consists of program usage guidelines, and a summary of program options.
	*/
	public static void printHelp()
	{
		printVersion(false);
		try
		{
			URL help = Main.class.getResource("/org/tamalin/babel/resources/help.txt");
			URLConnection connection = help.openConnection();
			Scanner in = new Scanner(connection.getInputStream());
			while(in.hasNextLine())
			{
				System.out.println(in.nextLine());
			}
		}
		catch(IOException ex)
		{
			Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Unable to open console help file.", ex);
		}
	}

	/**
	* Output version information to the console.
	* Copyright information is included if the copyright parameter is 'true'.
	* @param copyright specifies if the summary of copyright information should be included in the output
	*/
	public static void printVersion(boolean copyright)
	{
		System.out.println("Babel Sequence Generator " + VERSION);
		System.out.println("Copyright (C) Quytelda K. Gaiwin\n");

		if(!copyright) return;

		System.out.println("This program is released under the terms of the Apache 2.0 License.");
		System.out.println("For more information, refer to the LICENSE and NOTICE files\n"
				+ "contained in the top level of the installation or JAR file.");
	}
	
	/** Specifies the version number of the program, as will be outputted by the --version option.
		In the trunk, the number will be set to the -next- upcoming release number, but specified as unstable.
		Specific stable release tags will not include the unstable designation. */
	public static final String VERSION = "2.0-unstable";

	/** Represent whether strict checking is enabled with -s or --strict.
	    If strict checking is enabled, the program will die upon encountering an invalid sequence template element.
	    Otherwise, the invalid element will simply be ignored, and the program will continue. */
	private static boolean strict = false;
}
